package com.geor.gcv.core.extract;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import com.geor.gcv.core.mat.Mats;
import com.geor.gcv.core.model.Area;
import com.geor.gcv.core.process.GrayProcess;
import com.geor.gcv.core.process.InputProcess;
import com.geor.gcv.core.process.ResizeProcess;

/**
 * 条形码区域提取
 * 
 * @author geor.lusongnan
 *
 */
public class CodeExtractor extends AbstractExtractor {
	private float scale = 0.6f;
	private List<Area> areas = new ArrayList<Area>();

	public CodeExtractor(float scale) {
		super();
		this.scale = scale;
	}

	@Override
	public void execute(Mat mat) {
		Mat resize = mat.clone();
		// 调整大小提高效率
		resize = new ResizeProcess(new InputProcess(mat), scale).process();
		// 灰度图片
		Mat gray = new GrayProcess(new InputProcess(resize)).process();
		// Highgui.imwrite("a.png", gray);
		// 高斯滤波
		Mat dst = new Mat();
		Imgproc.GaussianBlur(gray, dst, new Size(5, 5), 0);
		Mats.releseMat(gray);
		// Highgui.imwrite("b.png", dst);

		// Sobel求得水平和垂直方向灰度图像的梯度差,使用Sobel算子
		Mat dst_x = new Mat();
		Mat abs_dst_x = new Mat();
		int scale = 1;
		int delta = 0;
		Imgproc.Sobel(dst, dst_x, CvType.CV_16S, 1, 0, 3, scale, delta, Core.BORDER_DEFAULT);
		// Highgui.imwrite("c1.png", dst_x);
		Core.convertScaleAbs(dst_x, abs_dst_x, 1, 0);
		// Highgui.imwrite("c2.png", abs_dst_x);

		Mat dst_y = new Mat();
		Mat abs_dst_y = new Mat();
		Imgproc.Sobel(dst, dst_y, CvType.CV_16S, 0, 1, 3, scale, delta, Core.BORDER_DEFAULT);
		// Highgui.imwrite("d1.png", dst_y);
		Core.convertScaleAbs(dst_y, abs_dst_y, 1, 0);
		// Highgui.imwrite("d2.png", abs_dst_y);

		Mat result_gary = new Mat();
		Core.subtract(abs_dst_x, abs_dst_y, result_gary);
		Mats.releseMat(dst_x);
		Mats.releseMat(abs_dst_x);
		Mats.releseMat(dst_y);
		Mats.releseMat(abs_dst_x);
		Mats.releseMat(dst);

		// 均值滤波，消除高频噪声
		Mat avg = new Mat();
		Imgproc.blur(result_gary, avg, new Size(5, 5), new Point(-1, -1));
		// Highgui.imwrite("f.png", avg);
		Mats.releseMat(abs_dst_x);

		// 二值化
		Mat bin = new Mat(avg.size(), CvType.CV_8UC1);
		for (int row = 0; row < avg.rows(); row++) {
			for (int col = 0; col < avg.cols(); col++) {
				int pex = (int) avg.get(row, col)[0];
				if (pex > 30) {
					bin.put(row, col, 255);
				} else {
					bin.put(row, col, 0);
				}
			}
		}
		// Highgui.imwrite("g.png", bin);
		Mats.releseMat(avg);
		// 闭运算
		Mat close = new Mat();
		Mat close_m = new Mat(3, 3, CvType.CV_8UC1);
		Imgproc.morphologyEx(bin, close, Imgproc.MORPH_CLOSE, close_m, new Point(-1, -1), 1);
		// Highgui.imwrite("H.png", close);
		Mats.releseMat(bin);
		Mats.releseMat(close_m);
		// 腐蚀，去除孤立的点
		Mat fus = new Mat();
		Mat fus_m = new Mat(10, 10, CvType.CV_8U);
		Imgproc.erode(close, fus, fus_m, new Point(-1, -1), 2);
		// Highgui.imwrite("I.png", fus);
		Mats.releseMat(fus_m);
		Mats.releseMat(close);

		// 膨胀，填充条形码间空隙，根据核的大小，有可能需要2~3次膨胀操作
		Mat pz = new Mat();
		Mat pz_m = new Mat(12, 12, CvType.CV_8U);
		Imgproc.dilate(fus, pz, pz_m);
		// Highgui.imwrite("j1.png", pz);
		Imgproc.dilate(pz, pz, pz_m);
		// Highgui.imwrite("j2.png", pz);
		Imgproc.dilate(pz, pz, pz_m);
		// Highgui.imwrite("j3.png", pz);
		Mats.releseMat(fus);
		Mats.releseMat(pz_m);

		// 通过findContours找到条形码区域的矩形边界
		List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
		Mat pzTmp = pz.clone();
		try {
			Imgproc.findContours(pzTmp, contours, new Mat(), Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_NONE);
		} catch (Exception e) {
		}

		for (MatOfPoint point : contours) {
			if (point.rows() != 0 && point.cols() != 0) {
				Rect rect = Imgproc.boundingRect(point);
				areas.add(new Area().toAreaByRect(rect).expand((float) mat.rows() / resize.rows()));
				Mats.releseMat(point);
			}
		}
		Mats.releseMat(pzTmp);
		Mats.releseMat(pz);
		Mats.releseMat(resize);

		Collections.sort(areas, new Comparator<Area>() {
			@Override
			public int compare(Area area1, Area area2) {
				if (area1.getArea() > area2.getArea()) {
					return -1;
				}
				if (area1.getArea() < area2.getArea()) {
					return 1;
				}
				return 0;
			}
		});

		Iterator<Area> it = areas.iterator();
		int index = 0;

		List<Area> realArea = new ArrayList<Area>();
		while (index < 3 && it.hasNext()) {
			Area area = it.next();
			if (area.getHeight() < area.getWidth()) {
				realArea.add(area);
			}
			index++;
		}
		areas.clear();
		areas.addAll(realArea);
		// 根据比例排序
		Collections.sort(areas, new Comparator<Area>() {
			@Override
			public int compare(Area o1, Area o2) {
				return (float) o1.getWidth() / o1.getHeight() < (float) o2.getWidth() / o2.getHeight() ? 1 : -1;
			}
		});
		Mats.releseMat(mat);
	}

	@Override
	public List<Area> get() {
		return areas;
	}

}
